 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.internal.runtime;

 import java.util.*;
 import org.eclipse.core.runtime.IAdapterFactory;
 import org.eclipse.core.runtime.IAdapterManager;

 /**
  * This class is the standard implementation of <code>IAdapterManager</code>. It provides
  * fast lookup of property values with the following semantics:
  * <ul>
  * <li>At most one factory will be invoked per property lookup
  * <li>If multiple installed factories provide the same adapter, only the first found in
  * the search order will be invoked.
  * <li>The search order from a class with the definition <br>
  * <code>class X extends Y implements A, B</code><br> is as follows: <il>
  * <li>the target's class: X
  * <li>X's superclasses in order to <code>Object</code>
  * <li>a breadth-first traversal of the target class's interfaces in the order returned by
  * <code>getInterfaces</code> (in the example, A and its superinterfaces then B and its
  * superinterfaces) </il>
  * </ul>
  *
  * @see IAdapterFactory
  * @see IAdapterManager
  */
 public final class AdapterManager implements IAdapterManager {
     /**
      * Cache of adapters for a given adaptable class. Maps String -> Map
      * (adaptable class name -> (adapter class name -> factory instance))
      * Thread safety note: The outer map is synchronized using a synchronized
      * map wrapper class. The inner map is not synchronized, but it is immutable
      * so synchronization is not necessary.
      */
     private Map adapterLookup;

     /**
      * Cache of classes for a given type name. Avoids too many loadClass calls.
      * (factory -> (type name -> Class)).
      * Thread safety note: Since this structure is a nested hash map, and both
      * the inner and outer maps are mutable, access to this entire structure is
      * controlled by the classLookupLock field. Note the field can still be
      * nulled concurrently without holding the lock.
      */
     private Map classLookup;

     /**
      * The lock object controlling access to the classLookup data structure.
      */
     private final Object classLookupLock = new Object ();

     /**
      * Cache of class lookup order (Class -> Class[]). This avoids having to compute often, and
      * provides clients with quick lookup for instanceOf checks based on type name.
      * Thread safety note: The map is synchronized using a synchronized
      * map wrapper class. The arrays within the map are immutable.
      */
     private Map classSearchOrderLookup;

     /**
      * Map of factories, keyed by <code>String</code>, fully qualified class name of
      * the adaptable class that the factory provides adapters for. Value is a <code>List</code>
      * of <code>IAdapterFactory</code>.
      */
     private final HashMap factories;

     private final ArrayList lazyFactoryProviders;

     private static final AdapterManager singleton = new AdapterManager();

     public static AdapterManager getDefault() {
         return singleton;
     }

     /**
      * Private constructor to block instance creation.
      */
     private AdapterManager() {
         factories = new HashMap(5);
         lazyFactoryProviders = new ArrayList(1);
     }

     /**
      * Given a type name, add all of the factories that respond to those types into
      * the given table. Each entry will be keyed by the adapter class name (supplied in
      * IAdapterFactory.getAdapterList).
      */
     private void addFactoriesFor(String typeName, Map table) {
         List factoryList = (List) getFactories().get(typeName);
         if (factoryList == null)
             return;
         for (int i = 0, imax = factoryList.size(); i < imax; i++) {
             IAdapterFactory factory = (IAdapterFactory) factoryList.get(i);
             if (factory instanceof IAdapterFactoryExt) {
                 String [] adapters = ((IAdapterFactoryExt) factory).getAdapterNames();
                 for (int j = 0; j < adapters.length; j++) {
                     if (table.get(adapters[j]) == null)
                         table.put(adapters[j], factory);
                 }
             } else {
                 Class [] adapters = factory.getAdapterList();
                 for (int j = 0; j < adapters.length; j++) {
                     String adapterName = adapters[j].getName();
                     if (table.get(adapterName) == null)
                         table.put(adapterName, factory);
                 }
             }
         }
     }

     private void cacheClassLookup(IAdapterFactory factory, Class clazz) {
         synchronized (classLookupLock) {
             //cache reference to lookup to protect against concurrent flush
 Map lookup = classLookup;
             if (lookup == null)
                 classLookup = lookup = new HashMap(4);
             HashMap classes = (HashMap) lookup.get(factory);
             if (classes == null) {
                 classes = new HashMap(4);
                 lookup.put(factory, classes);
             }
             classes.put(clazz.getName(), clazz);
         }
     }

     private Class cachedClassForName(IAdapterFactory factory, String typeName) {
         synchronized (classLookupLock) {
             Class clazz = null;
             //cache reference to lookup to protect against concurrent flush
 Map lookup = classLookup;
             if (lookup != null) {
                 HashMap classes = (HashMap) lookup.get(factory);
                 if (classes != null) {
                     clazz = (Class ) classes.get(typeName);
                 }
             }
             return clazz;
         }
     }

     /**
      * Returns the class with the given fully qualified name, or null
      * if that class does not exist or belongs to a plug-in that has not
      * yet been loaded.
      */
     private Class classForName(IAdapterFactory factory, String typeName) {
         Class clazz = cachedClassForName(factory, typeName);
         if (clazz == null) {
             try {
                 if (factory instanceof IAdapterFactoryExt)
                     factory = ((IAdapterFactoryExt) factory).loadFactory(false);
                 if (factory != null) {
                     clazz = factory.getClass().getClassLoader().loadClass(typeName);
                     cacheClassLookup(factory, clazz);
                 }
             } catch (ClassNotFoundException e) {
                 // class not yet loaded
 }
         }
         return clazz;
     }

     /* (non-Javadoc)
      * @see org.eclipse.core.runtime.IAdapterManager#getAdapterTypes(java.lang.Class)
      */
     public String [] computeAdapterTypes(Class adaptable) {
         Set types = getFactories(adaptable).keySet();
         return (String []) types.toArray(new String [types.size()]);
     }

     /**
      * Computes the adapters that the provided class can adapt to, along
      * with the factory object that can perform that transformation. Returns
      * a table of adapter class name to factory object.
      * @param adaptable
      */
     private Map getFactories(Class adaptable) {
         //cache reference to lookup to protect against concurrent flush
 Map lookup = adapterLookup;
         if (lookup == null)
             adapterLookup = lookup = Collections.synchronizedMap(new HashMap(30));
         Map table = (Map) lookup.get(adaptable.getName());
         if (table == null) {
             // calculate adapters for the class
 table = new HashMap(4);
             Class [] classes = computeClassOrder(adaptable);
             for (int i = 0; i < classes.length; i++)
                 addFactoriesFor(classes[i].getName(), table);
             // cache the table
 lookup.put(adaptable.getName(), table);
         }
         return table;
     }

     public Class [] computeClassOrder(Class adaptable) {
         Class [] classes = null;
         //cache reference to lookup to protect against concurrent flush
 Map lookup = classSearchOrderLookup;
         if (lookup == null)
             classSearchOrderLookup = lookup = Collections.synchronizedMap(new HashMap());
         else
             classes = (Class []) lookup.get(adaptable);
         // compute class order only if it hasn't been cached before
 if (classes == null) {
             ArrayList classList = new ArrayList();
             computeClassOrder(adaptable, classList);
             classes = (Class []) classList.toArray(new Class [classList.size()]);
             lookup.put(adaptable, classes);
         }
         return classes;
     }

     /**
      * Builds and returns a table of adapters for the given adaptable type.
      * The table is keyed by adapter class name. The
      * value is the <b>sole<b> factory that defines that adapter. Note that
      * if multiple adapters technically define the same property, only the
      * first found in the search order is considered.
      *
      * Note that it is important to maintain a consistent class and interface
      * lookup order. See the class comment for more details.
      */
     private void computeClassOrder(Class adaptable, Collection classes) {
         Class clazz = adaptable;
         Set seen = new HashSet(4);
         while (clazz != null) {
             classes.add(clazz);
             computeInterfaceOrder(clazz.getInterfaces(), classes, seen);
             clazz = clazz.getSuperclass();
         }
     }

     private void computeInterfaceOrder(Class [] interfaces, Collection classes, Set seen) {
         List newInterfaces = new ArrayList(interfaces.length);
         for (int i = 0; i < interfaces.length; i++) {
             Class interfac = interfaces[i];
             if (seen.add(interfac)) {
                 //note we cannot recurse here without changing the resulting interface order
 classes.add(interfac);
                 newInterfaces.add(interfac);
             }
         }
         for (Iterator it = newInterfaces.iterator(); it.hasNext();)
             computeInterfaceOrder(((Class ) it.next()).getInterfaces(), classes, seen);
     }

     /**
      * Flushes the cache of adapter search paths. This is generally required whenever an
      * adapter is added or removed.
      * <p>
      * It is likely easier to just toss the whole cache rather than trying to be smart
      * and remove only those entries affected.
      * </p>
      */
     public synchronized void flushLookup() {
         adapterLookup = null;
         classLookup = null;
         classSearchOrderLookup = null;
     }

     /* (non-Javadoc)
      * @see org.eclipse.core.runtime.IAdapterManager#getAdapter(java.lang.Object, java.lang.Class)
      */
     public Object getAdapter(Object adaptable, Class adapterType) {
         IAdapterFactory factory = (IAdapterFactory) getFactories(adaptable.getClass()).get(adapterType.getName());
         Object result = null;
         if (factory != null)
             result = factory.getAdapter(adaptable, adapterType);
         if (result == null && adapterType.isInstance(adaptable))
             return adaptable;
         return result;
     }

     /* (non-Javadoc)
      * @see org.eclipse.core.runtime.IAdapterManager#getAdapter(java.lang.Object, java.lang.Class)
      */
     public Object getAdapter(Object adaptable, String adapterType) {
         return getAdapter(adaptable, adapterType, false);
     }

     /**
      * Returns an adapter of the given type for the provided adapter.
      * @param adaptable the object to adapt
      * @param adapterType the type to adapt the object to
      * @param force <code>true</code> if the plug-in providing the
      * factory should be activated if necessary. <code>false</code>
      * if no plugin activations are desired.
      */
     private Object getAdapter(Object adaptable, String adapterType, boolean force) {
         IAdapterFactory factory = (IAdapterFactory) getFactories(adaptable.getClass()).get(adapterType);
         if (force && factory instanceof IAdapterFactoryExt)
             factory = ((IAdapterFactoryExt) factory).loadFactory(true);
         Object result = null;
         if (factory != null) {
             Class clazz = classForName(factory, adapterType);
             if (clazz != null)
                 result = factory.getAdapter(adaptable, clazz);
         }
         if (result == null && adaptable.getClass().getName().equals(adapterType))
             return adaptable;
         return result;
     }

     public boolean hasAdapter(Object adaptable, String adapterTypeName) {
         return getFactories(adaptable.getClass()).get(adapterTypeName) != null;
     }

     /* (non-Javadoc)
      * @see org.eclipse.core.runtime.IAdapterManager#queryAdapter(java.lang.Object, java.lang.String)
      */
     public int queryAdapter(Object adaptable, String adapterTypeName) {
         IAdapterFactory factory = (IAdapterFactory) getFactories(adaptable.getClass()).get(adapterTypeName);
         if (factory == null)
             return NONE;
         if (factory instanceof IAdapterFactoryExt) {
             factory = ((IAdapterFactoryExt) factory).loadFactory(false); // don't force loading
 if (factory == null)
                 return NOT_LOADED;
         }
         return LOADED;
     }

     /* (non-Javadoc)
      * @see org.eclipse.core.runtime.IAdapterManager#loadAdapter(java.lang.Object, java.lang.String)
      */
     public Object loadAdapter(Object adaptable, String adapterTypeName) {
         return getAdapter(adaptable, adapterTypeName, true);
     }

     /*
      * @see IAdapterManager#registerAdapters
      */
     public synchronized void registerAdapters(IAdapterFactory factory, Class adaptable) {
         registerFactory(factory, adaptable.getName());
         flushLookup();
     }

     /*
      * @see IAdapterManager#registerAdapters
      */
     public void registerFactory(IAdapterFactory factory, String adaptableType) {
         List list = (List) factories.get(adaptableType);
         if (list == null) {
             list = new ArrayList(5);
             factories.put(adaptableType, list);
         }
         list.add(factory);
     }

     /*
      * @see IAdapterManager#unregisterAdapters
      */
     public synchronized void unregisterAdapters(IAdapterFactory factory) {
         for (Iterator it = factories.values().iterator(); it.hasNext();)
             ((List) it.next()).remove(factory);
         flushLookup();
     }

     /*
      * @see IAdapterManager#unregisterAdapters
      */
     public synchronized void unregisterAdapters(IAdapterFactory factory, Class adaptable) {
         List factoryList = (List) factories.get(adaptable.getName());
         if (factoryList == null)
             return;
         factoryList.remove(factory);
         flushLookup();
     }

     /*
      * Shuts down the adapter manager by removing all factories
      * and removing the registry change listener. Should only be
      * invoked during platform shutdown.
      */
     public synchronized void unregisterAllAdapters() {
         factories.clear();
         flushLookup();
     }

     public void registerLazyFactoryProvider(IAdapterManagerProvider factoryProvider) {
         synchronized (lazyFactoryProviders) {
             lazyFactoryProviders.add(factoryProvider);
         }
     }

     public boolean unregisterLazyFactoryProvider(IAdapterManagerProvider factoryProvider) {
         synchronized (lazyFactoryProviders) {
             return lazyFactoryProviders.remove(factoryProvider);
         }
     }

     public HashMap getFactories() {
         // avoid the synchronize if we don't have to call it
 if (lazyFactoryProviders.size() == 0)
             return factories;
         synchronized (lazyFactoryProviders) {
             while (lazyFactoryProviders.size() > 0) {
                 IAdapterManagerProvider provider = (IAdapterManagerProvider) lazyFactoryProviders.remove(0);
                 if (provider.addFactories(this))
                     flushLookup();
             }
         }
         return factories;
     }
 }

